#include "FKObject3D.h"

#include <QColor>

#include "FKDisplayReferent.h"

#include "FKLogger.h"
#include "FKEventNames.h"

FK_EVENTS(FKObject3D){
    props << FKEventName::c_components <<
             FKEventName::c_position <<
             FKEventName::c_rotation <<
             FKEventName::c_linearMoveing <<
             FKEventName::c_linearSpeed <<
             FKEventName::c_linearAccelerating <<
             FKEventName::c_linearAcceleration <<
             FKEventName::c_rotating <<
             FKEventName::c_rotatingSpeed <<
             FKEventName::c_rotatingAccelerating <<
             FKEventName::c_rotatingAcceleration <<
             FKEventName::c_color;
}

/*!
 * \class FKObject3D
 * \inmodule FKPhisics
 * \brief Класс реализует поведение трехмерных объектов.
 * Объекты класса представляют только физическую логику
 * и содержат общие данные о геометрии, используемые
 * ядром для обработки событий.
 * Косметическое анимирование и отрисовка выполняются
 * графическим референтом отдельно от самого объекта.
 */

/*!
 * \brief Creates 3d-object in room. Constructs only phisical object, display referent should be created after room loading.
 */

FKObject3D::FKObject3D(FKRoom* room,qint32 objectID):FKDynamicObject(room,objectID){
    FK_CBEGIN
     _referent=0;
    FK_CONSTRUCTOR
    FK_CEND
}

/*!
 * \brief Destructs 3d-object. Does not affect display referent.
 */

FKObject3D::~FKObject3D(){
    FK_DBEGIN
    FK_DESTRUCTOR
    FK_DEND
}

/*!
 * \brief Allow area to map the object by connecting it's moved() signal.
 * This function should be called only once per area. Second call will cause dublicate syncronization.
 */

void FKObject3D::bindToArea(QObject* area){
    connect(this,SIGNAL(moved(QVector3D,QVector3D)),area,SLOT(moveObject(QVector3D,QVector3D)));
}

/*!
 * \brief Set real position of the object and move components accordingly. For server-side only.
 */

void FKObject3D::setPosition(const QVector3D& position){
    if(servant){
        QVector3D prevPos(servant->position);
        QVector3D offset=position-prevPos;
        servant->position=position;
        emit moved(prevPos,position);
        updateProp(FKEventName::c_position);
        foreach(FKObject3D* obj, servant->components)obj->move(offset);
    }
}

/*!
 * \brief Set real position of object. Does not affects components. For server-side only.
 */

void FKObject3D::setSinglePosition(const QVector3D& position){
    if(servant){
        QVector3D prevPos(servant->position);
        servant->position=position;
        emit moved(prevPos,position);
        updateProp(FKEventName::c_position);
    }
}

/*!
 * \brief Move object and it's components.
 */

void FKObject3D::move(const QVector3D& offset){
    if(servant){
        QVector3D prevPos(servant->position);
        servant->position+=offset;
        emit moved(prevPos,servant->position);
        updateProp(FKEventName::c_position);
        foreach(FKObject3D* obj, servant->components)obj->move(offset);
    }
}

/*!
 * \brief Move object. Do not affect components
 */

void FKObject3D::moveSingle(const QVector3D& offset){
    if(servant){
        servant->position+=offset;
        updateProp(FKEventName::c_position);
        foreach(FKObject3D* obj, servant->components)obj->move(offset);
    }
}

/*!
 * \brief Returns position of the object. For client-side only
 */

QVector3D FKObject3D::position()const{
    if(customValues)return customValues->position;
    return QVector3D();
}

/*!
 * \brief Set rotation for object. Does not affects components. Server-side only.
 */

void FKObject3D::setSingleRotation(const QVector4D& rotation){
    if(servant){
        servant->rotation=rotation;
        updateProp(FKEventName::c_rotation);
    }
}

/*!
 * \brief Turn object. Does not affect components. For server-side onky.
 */

void FKObject3D::rotateSingle(const QVector4D& way){
    if(servant){
        servant->rotation=(QQuaternion(servant->rotation)*QQuaternion(way)).toVector4D();
        updateProp(FKEventName::c_rotation);
    }
}

/*!
 * \brief Returns rotation as 4d vector. For client-side only
 */

QVector4D FKObject3D::rotation()const{
    if(customValues)return customValues->rotation;
    return QVector4D();
}

/*!
 * \brief Allow or forbid linear moving autocalculation. For server-side only.
 */

void FKObject3D::setLinearMoveing(const bool& t){
    if(servant){
        servant->move=t;
        updateProp(FKEventName::c_linearMoveing);
    }
}

/*!
 * \brief Returns true if linear move autocalculation is active, overwise returns false. For client-side only.
 */

bool FKObject3D::linearMoveing()const{
    if(customValues)return customValues->move;
    return false;
}

/*!
 * \brief Set object's speed vector. If linear moveing is enabled, the system in each frame moves object by speed vector per millisecond.
 * This is should be called at server-side, but apply for client-side too. When objects are moveing by this way, client-server syncronization does not occure before applyUpdates() call.
 * Thats why this moveing behaivour has low traffic cost.
 */

void FKObject3D::setLinearSpeed(const QVector3D& speed){
    if(servant){
        servant->linearSpeed=speed;
        updateProp(FKEventName::c_linearSpeed);
    }
}

/*!
 * \brief Returns linear speed vector that move object per millisecind. This is client-side function.
 */

QVector3D FKObject3D::linearSpeed()const{
    if(customValues)return customValues->linearSpeed;
    return QVector3D();
}

/*!
 * \brief Allow or forbid autochange linear speed by accelerating vector. This is server-side function.
 */

void FKObject3D::setLinearAccelerating(const bool& t){
    if(servant){
        servant->accelerateMoveing=t;
        updateProp(FKEventName::c_linearAccelerating);
    }
}

/*!
 * \brief Return true if linear speed acceleration is allowed. This is client-side function.
 */

bool FKObject3D::linearAccelerating()const{
    if(customValues)return customValues->accelerateMoveing;
    return false;
}

/*!
 * \brief Set linear speed accelerating vector. If speed acceleration is allowed,
 * the system change linear speed in each frame by accelerating vector per millisecond.
 * This is server-side function, but apply for client-side too. Does not cause client-server syncronization.
 */

void FKObject3D::setLinearAcceleration(const QVector3D& acceleration){
    if(servant){
        servant->linearAcceleration=acceleration;
        updateProp(FKEventName::c_linearAcceleration);
    }
}

/*!
 * \brief Returns linear speed accelerating vector. This is client-side function.
 */

QVector3D FKObject3D::linearAcceleration()const{
    if(customValues)return customValues->linearAcceleration;
    return QVector3D();
}

/*!
 * \brief Allow or forbid auto rotation. This is server-side function.
 */

void FKObject3D::setRotating(const bool& t){
    if(servant){
        servant->rotate=t;
        updateProp(FKEventName::c_rotating);
    }
}

/*!
 * \brief Returns true if auto rotating is allowed. This is client-side function.
 */

bool FKObject3D::rotating()const{
    if(customValues)return customValues->rotate;
    return false;
}

/*!
 * \brief Set rotation speed vector. If auto rotation is allowed, the system in each frame
 * turns object by vector-based quaternion per second.
 * This is server-side function, but apply client-side too. Does not cause client-server syncronization.
 */

void FKObject3D::setRotationSpeed(const QVector4D& rotation){
    if(servant){
        servant->rotationSpeed=rotation;
        updateProp(FKEventName::c_rotatingSpeed);
    }
}

/*!
 * \brief Returns rotation speed vector. This is client-side function.
 */

QVector4D FKObject3D::rotationSpeed()const{
    if(customValues)return customValues->rotationSpeed;
    return QVector4D();
}

/*!
 * \brief Allow or forbid rotation speed autoacceleration. This is server-side function.
 */

void FKObject3D::setRotationAccelerating(const bool& t){
    if(servant){
        servant->accelerateRotation=t;
        updateProp(FKEventName::c_rotatingAccelerating);
    }
}

/*!
 * \brief Returns true if rotation speed autoaccelerating is allowed. This is client-side function.
 */

bool FKObject3D::rotationAccelerating()const{
    if(customValues)return customValues->accelerateRotation;
    return false;
}

/*!
 * \brief Set rotation speed acceleration vector. If rotation accelerating is allowed,
 * the system turns rotation speed in each frame by vector-based quaternion per millisecond.
 * This is server-side function, but apply client-side too. Does not cause client-server syncronization.
 */

void FKObject3D::setRotationAcceleration(const QVector4D& acceleration){
    if(servant){
        servant->rotationAcceleration=acceleration;
        updateProp(FKEventName::c_rotatingAcceleration);
    }
}

/*!
 * \brief Returns rotation speed acceleration vector. This is client-side function.
 */

QVector4D FKObject3D::rotationAcceleration()const{
    if(customValues)return customValues->rotationAcceleration;
    return QVector4D();
}

/*!
 * \brief Set color for object. Also affects all components. Vector represents RGBA color of 0-1 float values. This is server-side function.
 */

void FKObject3D::setColor(const QVector4D& color){
    if(servant){
        servant->color=color;
        updateProp(FKEventName::c_color);
        foreach(FKObject3D* obj,servant->components)obj->setColor(color);
    }
}

/*!
 * \brief Multiply object's color by new color vector. Also affects all components. This is server-side function.
 */

void FKObject3D::blendColor(const QVector4D& color){
    if(servant){
        servant->color*=color;
        updateProp(FKEventName::c_color);
        foreach(FKObject3D* obj,servant->components)obj->blendColor(color);
    }
}

/*!
 * \brief Set color of object. Does not affects components. This is server-side function.
 */

void FKObject3D::setSingleColor(const QVector4D& color){
    if(servant){
        servant->color=color;
        updateProp(FKEventName::c_color);
    }
}

/*!
 * \brief Multiply object's color by new color vector. Does not affect components. This is server-side function.
 */

void FKObject3D::blendSingleColor(const QVector4D& color){
    if(servant){
        servant->color*=color;
        updateProp(FKEventName::c_color);
    }
}

/*!
 * \brief Returns object's color as RGBA vector of 0-1 float values. This is client-side function.
 */

QVector4D FKObject3D::color()const{
    if(customValues)return customValues->color;
    return QVector4D();
}

/*!
 * \brief Add another 3d-object as component for this object.
 * Components automaticly change their properties (position, color) accordingly this objects property changes.
 * This is server-side function, but applies client-side too.
 */

void FKObject3D::addComponent(FKObject3D* component){
    if(servant){
        servant->components.append(component);
        updateProp(FKEventName::c_components);
        setTotalWatching(component);
    }
}

void FKObject3D::customInitialization(){
//    installReferent();
}

void FKObject3D::refreshReferent(){
    C_position(customValues->position);
    C_rotation(customValues->rotation);
    C_color(customValues->color);
}

void FKObject3D::customDeinitialization(){
    if(_referent){
        _referent->deleteLater();
        _referent=0;
    }
}

void FKObject3D::recalculate(const qint32 &ms){
    if(servant){
        if(servant->move){
            QVector3D offset=ms*servant->linearSpeed;
            autoMoveReal(offset);
        }
        if(servant->accelerateMoveing){
            servant->linearSpeed+=ms * servant->linearAcceleration;
            updateProp(FKEventName::c_linearSpeed);
            C_linearSpeed(servant->linearSpeed);
        }
        if(servant->rotate){
            QQuaternion trn(servant->rotationSpeed);
            trn.setScalar(trn.scalar()*ms);
            servant->rotation=(QQuaternion(servant->rotation)*trn).toVector4D();
            updateProp(FKEventName::c_rotation);
            C_rotation(servant->rotation);
        }
        if(servant->accelerateRotation){
            QQuaternion trn(servant->rotationAcceleration);
            trn.setScalar(trn.scalar()*ms);
            servant->rotationSpeed=(QQuaternion(servant->rotationSpeed)*trn).toVector4D();
            updateProp(FKEventName::c_rotatingSpeed);
            C_rotationSpeed(servant->rotationSpeed);
        }
    }else if(customValues){
        if(customValues->move){
            QVector3D offset=ms*customValues->linearSpeed;
            autoMoveVisual(offset);
        }
        if(customValues->accelerateMoveing){
            customValues->linearSpeed+=ms*customValues->linearAcceleration;
        }
        if(customValues->rotate){
            QQuaternion trn(customValues->rotationSpeed);
            trn.setScalar(trn.scalar()*ms);
            QQuaternion q(QQuaternion(customValues->rotation)*trn);
            customValues->rotation=q.toVector4D();
            if(_referent)_referent->setRotation(q);
        }
        if(customValues->accelerateRotation){
            QQuaternion trn(customValues->rotationAcceleration);
            trn.setScalar(trn.scalar()*ms);
            customValues->rotationSpeed=(QQuaternion(customValues->rotationSpeed)*trn).toVector4D();
        }
    }
}

/*!
   \fn float size()const
   \brief Returns object's size (radius). Size used when selecting from area. This is server-side function.
 */

/*!
   \fn QVector3D getPosition()const
   \brief Returns real object's position. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn QVector4D getRotation()const
   \brief Returns real object's rotation. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn bool getLinearMoveing()const
   \brief Returns true if linear moving is allowed. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn QVector3D getLinearSpeed()const
   \brief Returns real object's linear speed. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn bool getLinearAccelerating()const
   \brief Returns true if linear accelerating is allowed. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn QVector3D getLinearAcceleration()const
   \brief Returns real object's linear acceleration. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn bool getRotating()const
   \brief Returns true if rotating is allowed. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn QVector4D getRotationSpeed()const
   \brief Returns real object's rotation speed. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn bool getRotationAccelerating()const
   \brief Returns true if rotation accelerating is allowed. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn QVector4D getRotationAcceleration()const
   \brief Returns real object's rotation acceleration. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn QVector4D getColor()const
   \brief Returns real object's color vector. Value always return real value, independent from watching settings. This is server-side function.
 */

/*!
   \fn void setDisplayReferent(FKDisplayReferent* referent)
   \brief Set display referent for object. Display referent should be created in initialization() function. This is both client and server-side function.
 */

/*!
   \fn FKDisplayReferent* displayReferent()const
   \brief Returns display referent of this object. This is both client and server-side function.
 */

/*!
   \fn QList<FKObject3D*> components()const
   \brief Returns list of object's components. This is client-side function.
 */

/*!
   \fn void setSize(const float& s)
   \brief Set size of object. When selecting from area, object would be selected, if circle of size radius cross the selected region
 */

//private functions:

void FKObject3D::autoMoveReal(const QVector3D &offset){
    QVector3D prevPos(servant->position);
    servant->position+=offset;
    emit moved(prevPos,servant->position);
    updateProp(FKEventName::c_position);
    C_position(servant->position);
    foreach(FKObject3D* obj,servant->components)obj->autoMoveReal(offset);
}

void FKObject3D::autoMoveVisual(const QVector3D &offset){
    customValues->position+=offset;
    if(_referent)_referent->setPos(customValues->position);
    foreach (FKObject3D* obj, customValues->components)obj->autoMoveVisual(offset);
}

QList<qint32> FKObject3D::getComponents()const{
    QList<qint32> lst;
    if(servant){
        foreach (FKObject3D* obj, servant->components) {
            lst.append(obj->getID());
        }
    }else{
        FK_MLOG("unable get 3d object components: no servant")
    }
    return lst;
}

void FKObject3D::C_components(const QList<qint32>& newComponents){
    FKObject3D* obj;
    customValues->components.clear();
    foreach(qint32 id,newComponents){
        obj=qobject_cast<FKObject3D*>(getObject(id));
        if(obj)customValues->components.append(obj);
    }
}

void FKObject3D::C_position(const QVector3D& position){
    if(customValues){
        customValues->position=position;
        if(_referent)_referent->setPos(position);
    }else{
        FK_MLOG("unable set position of 3d object: no custom values")
    }
}

void FKObject3D::C_rotation(const QVector4D& rotation){
    if(customValues){
        customValues->rotation=rotation;
        if(_referent)_referent->setRotation(QQuaternion(rotation));
    }else{
        FK_MLOG("unable set rotation of 3d object: no custom values")
    }
}

void FKObject3D::C_linearMoveing(const bool& t){
    if(customValues){
        customValues->move=t;
    }else{
        FK_MLOG("unable set moveing of 3d object: no custom values")
    }
}

void FKObject3D::C_linearSpeed(const QVector3D& speed){
    if(customValues){
        customValues->linearSpeed=speed;
    }else{
        FK_MLOG("unable set linear moveing of 3d object: no custom values")
    }
}

void FKObject3D::C_linearAccelerating(const bool& t){
    if(customValues){
        customValues->accelerateMoveing=t;
    }else{
        FK_MLOG("unable set linear accelerating of 3d object: no custom values")
    }
}

void FKObject3D::C_linearAcceleration(const QVector3D& acceleration){
    if(customValues){
        customValues->linearAcceleration=acceleration;
    }else{
        FK_MLOG("unable set linear acceleration of 3d object: no custom values")
    }
}

void FKObject3D::C_rotating(const bool& t){
    if(customValues){
        customValues->rotate=t;
    }else{
        FK_MLOG("unable set rotating of 3d object: no custom values")
    }
}

void FKObject3D::C_rotationSpeed(const QVector4D& speed){
    if(customValues){
        customValues->rotationSpeed=speed;
    }else{
        FK_MLOG("unable set rotating speed of 3d object: no custom values")
    }
}

void FKObject3D::C_rotationAccelerating(const bool& t){
    if(customValues){
        customValues->accelerateRotation=t;
    }else{
        FK_MLOG("unable set rotation accelerating of 3d object: no custom values")
    }
}

void FKObject3D::C_rotationAcceleration(const QVector4D& acceleration){
    if(customValues){
        customValues->rotationAcceleration=acceleration;
    }else{
        FK_MLOG("unable set rotation acceleration of 3d object: no custom values")
    }
}

void FKObject3D::C_color(const QVector4D& color){
    if(customValues){
        customValues->color=color;
        if(_referent)_referent->setColor(QColor(color.x(),color.y(),color.z(),color.w()));
    }else{
        FK_MLOG("unable set rotation acceleration of 3d object: no custom values")
    }
}
